#include "SpectraSTLib.hpp"
#include "SpectraSTSearchTask.hpp"
#include "SpectraSTSearchParams.hpp"
#include "SpectraSTCreateParams.hpp"
#include "SpectraSTFileList.hpp"
#include "SpectraSTLog.hpp"
#include "SpectraSTConstants.hpp"
#include "FileUtils.hpp"
#include "Peptide.hpp"

/*
 * HU: MS2 spectra prediction
 */
#include "SpectraSTParams.hpp"
#include "SpectraSTTrainModelParams.hpp"
#include "SpectraSTPredictionModelParams.hpp"
#include "SpectraSTModel.hpp"
#include "SpectraSTModelRankBoost.hpp"

#ifndef STANDALONE_LINUX
#include "common/hooks_tpp.h"
#endif

#include <string>
#include <time.h>
#include <fstream>
#include <sstream>

#include <math.h>

#include "SpectraSTFastaFileHandler.hpp"

/*

 Program       : Spectrast
 Author        : Henry Lam <hlam@systemsbiology.org>
 Date          : 03.06.06


 Copyright (C) 2006 Henry Lam

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA

 Henry Lam
 Insitute for Systems Biology
 1441 North 34th St.
 Seattle, WA  98103  USA
 hlam@systemsbiology.org

 */

/* Class: SpectraSTMain
 * 
 * main function for SpectraST.
 * 
 */

using namespace std;

static void doCreate(SpectraSTCreateParams& createParams,
		vector<string>& fileNames);
static unsigned int doSearch(SpectraSTSearchParams& searchParams,
		vector<string>& fileNames);
static void doTrainModel(SpectraSTTrainModelParams* tarinModelParams,
		vector<string>& fileNames);
static void doPrediction(SpectraSTPredictionModelParams* predictionModelParams,
		vector<string>& fileNames);
static void printUsage();
static void readUserModFile(string& modFileName);

// Verbose and quiet option flags. Don't want to pass them everywhere, so use global variables
bool g_verbose;
bool g_quiet;

// global pointer to a log object for keeping a log file
SpectraSTLog* g_log;

#ifndef LIB_BUILD
// main
int main(int argc, char* argv[]) {

#ifndef STANDALONE_LINUX
	hooks_tpp handler(argc, argv); // set up install paths etc
#endif

	// remember the time
	time_t startTime = time(NULL);
	char* startTimeStr = ctime(&startTime);
	startTimeStr[strlen(startTimeStr) - 1] = '\0';
	string logFileName("spectrast.log");
	string modFileName("spectrast.usermods");

	Peptide::defaultTables();

	// remember the command line to be put in the log file
	stringstream commandLiness;
	for (int ar = 0; ar < argc; ar++) {
		commandLiness << argv[ar] << ' ';
	}

	char mode = '\0';
	g_verbose = false;
	g_quiet = false;

	SpectraSTCreateParams createParams;
	SpectraSTSearchParams searchParams;
	SpectraSTParams* params;

	if (argc < 2) {
		printUsage();
		return (1);
	}

	// get all options
	int i;
	string expectArg = "";

	for (i = 1; i < argc; i++) {

		//cout << "i=" << i << "; ar=" << argv[i] << endl;
		if (argv[i][0] == '-') {

			if (strlen(argv[i]) <= 1)
				continue; // a '-' by itself. Ignore.

			// it's an option, the char immediately following the '-' tells us what
			// kind of option it is
			char optionType = argv[i][1];

			if (optionType == 's' || optionType == 'c' || optionType == 't'
					|| optionType == 'p') {
				// the mode (Search or Create) is set once the program sees an option
				// of that type. e.g. if it sees a -sF option, the program will assume
				// it's in Search mode. Thus, no need to explicitly set the mode in
				// most cases, although an option of -s or -c is also legal and simply tells the program to
				// run in that mode using all defaults.

				if (!mode) {
					mode = optionType;
					if (optionType == 't') {
						params = new SpectraSTTrainModelParams();
					} else if (optionType == 'p') {
						params = new SpectraSTPredictionModelParams();
					}
				} else if (mode != optionType) {
					// already chosen another mode!
					printUsage();
					return (1);
				}

			} else if (optionType == 'V') {
				//"spectrast -V"
				g_verbose = true;
				// note: verbose overrides quiet
				if (g_quiet)
					g_quiet = false;
				expectArg = "";

			} else if (optionType == 'Q' && !g_verbose) {
				g_quiet = true;
				expectArg = "";

			} else if (optionType == 'L') {
				// user-specified log file name
				logFileName = strlen(argv[i]) > 2 ? (argv[i] + 2) : "";
				if (logFileName.empty()) {
					expectArg = "L";
				} else {
					expectArg = "";
					fixpath(logFileName);
				}
			} else if (optionType == 'M') {
				// user-specified usermods file name
				modFileName = strlen(argv[i]) > 2 ? (argv[i] + 2) : "";
				if (modFileName.empty()) {
					expectArg = "M";
				} else {
					expectArg = "";
					fixpath(modFileName);
				}
			}

			if (optionType == 's' || optionType == 'c' || optionType == 't'
					|| optionType == 'p') {
				// sub-options, in the form -s? or -c? where ? is the sub-option of mode s or c
				string subop = strlen(argv[i]) > 2 ? (argv[i] + 2) : "";

				if (subop.empty())
					continue; // bare -s or -c. Ignore.

				if (mode == 's') {

					if (subop[0] == '_' && subop.length() <= 3) {
						searchParams.printAdvancedOptions(cerr);
						return (1);
					}
					if (!searchParams.addOption(subop)) {
						expectArg = "s" + subop;
					} else {
						expectArg = "";
					}

				} else if (mode == 'c') {

					if (subop[0] == '_' && subop.length() <= 3) {
						createParams.printAdvancedOptions(cerr);
						return (1);
					}
					if (!createParams.addOption(subop)) {
						expectArg = "c" + subop;
					} else {
						expectArg = "";
					}

				} else if (mode == 't') {
					if (subop[0] == '_' && subop.length() <= 3) {
						dynamic_cast<SpectraSTTrainModelParams*>(params)->printAdvancedOptions(
								cerr);
						return (1);
					}
					if (!params->addOption(subop)) {
						expectArg = "t" + subop;
					} else {
						expectArg = "";
					}
				} else if (mode == 'p') {
					if (subop[0] == '_' && subop.length() <= 3) {
						dynamic_cast<SpectraSTPredictionModelParams*>(params)->printAdvancedOptions(
								cerr);
						return (1);
					}
					if (!params->addOption(subop)) {
						expectArg = "p" + subop;
					} else {
						expectArg = "";
					}
				}
			}

		} else {
			// not an option.
			if (expectArg.empty()) {
				// the previous option is not expecting an argument.
				// since all options should preceed files, there should be no more options
				break;
			} else {
				string optionValue(argv[i]);

				if (expectArg == "L") {
					logFileName = optionValue;
				} else if (expectArg == "M") {
					modFileName = optionValue;
				} else if (expectArg[0] == 's') {
					bool dummy = searchParams.addOption(
							expectArg.substr(1) + optionValue);
				} else if (expectArg[0] == 'c') {
					bool dummy = createParams.addOption(
							expectArg.substr(1) + optionValue);
				} else if (expectArg[0] == 't') {
					bool dummy = params->addOption(
							expectArg.substr(1) + optionValue);
				} else if (expectArg[0] == 'p') {
					bool dummy = params->addOption(
							expectArg.substr(1) + optionValue);
				}
				expectArg = "";

			}
		}
	}

	// create the log object
	g_log = new SpectraSTLog(logFileName);

	// log the command line and the start time
	g_log->log("START", commandLiness.str(), startTimeStr);

	// log the file offset for this machine -- this defines whether the library can be bigger than ~2GB
	stringstream offsetSizess;
	offsetSizess << "File offset size is " << sizeof(fstream::off_type)
			<< " bytes.";
	if (sizeof(fstream::off_type) >= 8) {
		offsetSizess << " Big library supported.";
	}
	g_log->log("GENERAL", offsetSizess.str());

	stringstream ptrSizess;
	ptrSizess << "Pointer size is " << sizeof(int*) << " bytes.";
	g_log->log("GENERAL", ptrSizess.str());

	// read usermod file
	readUserModFile(modFileName);

	if (!g_quiet) {
		cout << "SpectraST started at " << startTimeStr << '.' << endl;
	}
	if (g_verbose) {
		cout << "VERBOSE MODE..." << endl;
	}

	// the index i should now point to the first non-option argument
	int firstNonOption = i;

	// there should be at least one more file following the options
	if (firstNonOption >= argc) {
		printUsage();
		return (1);
	}

	vector<string> fileNames;
	for (int j = firstNonOption; j < argc; j++) {
		string fn(argv[j]);
		fixpath(fn);
		fileNames.push_back(fn);
	}

	unsigned int searchCount = 0;

	if (mode == 'c') {

		doCreate(createParams, fileNames);

	} else if (mode == 's') {

		searchCount = doSearch(searchParams, fileNames);

	} else if (mode == 't') {
		doTrainModel(dynamic_cast<SpectraSTTrainModelParams*>(params),
				fileNames);
	} else if (mode == 'p') {
		doPrediction(dynamic_cast<SpectraSTPredictionModelParams*>(params),
				fileNames);
	} else {
		// no mode. must be list
		if (SpectraSTFileList::isFileList(fileNames) == 1) {
			// is indeed list. instantiating the SpectraSTFileList object will carry out the required actions (create or search)
			SpectraSTFileList* fileList = new SpectraSTFileList(fileNames);
			// in case it's a search, get the search count
			searchCount = fileList->getSearchCount();
			delete fileList;
		} else {
			g_log->error("GENERAL",
					"No mode (Create or Search) specified. Exiting.");
		}

	}

	Peptide::deleteTables();

	// Note the time at finish line, calculates how long the program runs.
	time_t endTime = time(NULL);
	char* endTimeStr = ctime(&endTime);
	endTimeStr[strlen(endTimeStr) - 1] = '\0';
	double timeElapsed = difftime(endTime, startTime);

	if (!g_quiet) {

		cout.precision(4);
		if (mode == 's' || searchCount > 0) {
			cout << "Total Number of Searches Performed = " << searchCount
					<< "; Run Time per Search = "
					<< timeElapsed / (double) (searchCount) << " seconds."
					<< endl;
		}
		cout.precision(4);
		cout << "Total Run Time = " << timeElapsed << " seconds." << endl;

		unsigned int numWarning = g_log->getNumWarning();
		if (numWarning != 0) {
			g_log->printWarnings();
		}

		unsigned int numError = g_log->getNumError();
		if (numError == 0) {
			cout << "SpectraST finished at " << endTimeStr << " without error."
					<< endl;
		} else {
			cout << "SpectraST finished at " << endTimeStr << " with "
					<< numError << " error(s):" << endl;
			g_log->printErrors();
			cout << endl;
		}
	}

	stringstream perfss;
	perfss << "Total Run Time = " << timeElapsed << " seconds.";
	if (mode == 's' || searchCount > 0) {
		perfss << " Total Number of Searches Performed = " << searchCount
				<< ". Run Time per Search = "
				<< timeElapsed / (double) searchCount << " seconds.";
	}
	// log the end time
	g_log->log("PERFORMANCE", perfss.str());
	g_log->log("END", commandLiness.str(), endTimeStr);
	g_log->log("==========");
	delete (g_log);

	/*
	 double retainedAve = g_retained / g_retainedCount;
	 cerr << "retained=" << retainedAve << " +/- " << sqrt(g_retainedSq / g_retainedCount - retainedAve * retainedAve) << endl;
	 */
	delete params;
	return (0);
}
#endif //LIB_BUILD
// doCreate - performs a Create mode task
void doCreate(SpectraSTCreateParams& createParams, vector<string>& fileNames) {

	// activate all create options
	createParams.finalizeOptions();

	int isList = SpectraSTFileList::isFileList(fileNames);
	if (isList < 0) {
		g_log->error("CREATE",
				"Input files are of mixed list and non-list types. Exiting.");
		return;
	}

	if (isList == 1) {
		// input files are lists. create a SpectraSTFileList object to do the create task
		SpectraSTFileList* fileList = new SpectraSTFileList(fileNames,
				createParams);
		delete (fileList);
	} else {
		// input files are individual files to be imported

		// instantiate a library; this will import the file and do everything
		SpectraSTLib* lib = new SpectraSTLib(fileNames, &createParams);
		delete (lib);
	}
}

// doSearch - performs a Search mode task
unsigned int doSearch(SpectraSTSearchParams& searchParams,
		vector<string>& fileNames) {

	// activate all search options
	searchParams.finalizeOptions();

	int isList = SpectraSTFileList::isFileList(fileNames);

	if (isList < 0) {
		g_log->error("CREATE",
				"Input files are of mixed list and non-list types. Exiting.");
		return (0);
	}

	if (isList == 1) {
		// input files are lists. create a SpectraSTFileList object to do the search task
		SpectraSTFileList* fileList = new SpectraSTFileList(fileNames,
				searchParams);
		unsigned int searchCount = fileList->getSearchCount();
		delete (fileList);
		return (searchCount);

	} else {

		// input files are individual files containing spectra to be searched.

		if (searchParams.libraryFile.empty()) {
			// no library file set!!
			g_log->error("SEARCH",
					"No library file specified. Cannot proceed with search.");
			return (0);
		}

		// instantiate the library
		SpectraSTLib* lib = new SpectraSTLib(searchParams.libraryFile,
				&searchParams);
		if (!g_quiet) {
			cout << "Library File loaded: \"" << searchParams.libraryFile
					<< "\"." << endl;
		}

		// create the search task and search it
		SpectraSTSearchTask* searchTask =
				SpectraSTSearchTask::createSpectraSTSearchTask(fileNames,
						searchParams, lib);
		unsigned int searchCount = 0;
		if (searchTask) {
			searchTask->preSearch();
			searchTask->search();
			searchTask->postSearch();
			searchCount = searchTask->getSearchCount();

			delete (searchTask);
		}
		delete (lib);

		return (searchCount);
	}
}

// calcTimeElapsed - calculates the time elapsed in seconds
double calcTimeElapsed(clock_t startTime) {
	clock_t endTime = clock();
	return ((double) (endTime - startTime) / CLOCKS_PER_SEC);
}

void readUserModFile(string& modFileName) {

	ifstream fin;
	unsigned int newTypeCount = 0;

	if (!myFileOpen(fin, modFileName)) {
		if (modFileName != "spectrast.usermods") {
			g_log->error("GENERAL",
					"Cannot open USERMODS file \"" + modFileName
							+ "\" for reading user-defined modifications. Using all defaults.");
		}
		return;
	}

	g_log->log("GENERAL",
			"Loading user-defined modifications from \"" + modFileName
					+ "\" .");

	string line("");
	while (nextLine(fin, line, "", "")) {
		if (line == "_EOF_") {
			return;
		}
		string::size_type pos = 0;
		string delimiter(" ,\t\r\n");
		//   string delimiter(" #{}\t\r\n");

		while (pos < line.length()) {

			string mod = nextToken(line, pos, pos, delimiter.c_str(), " \t");

			/*
			 if (line[pos] == '{') {
			 // {} coming
			 delimiter = "#}";
			 } else {
			 delimiter = " #{}\t\r\n";
			 }
			 */

			pos++;

			if (mod.empty())
				continue;

			string::size_type pipePos = 0;
			string token = nextToken(mod, pipePos, pipePos, " |", " ");
			string modMass = nextToken(mod, pipePos, pipePos, " |", " |");
			string modType = nextToken(mod, pipePos, pipePos, " |", " |");

			string allAA(ALL_AA);
			allAA += "nc";
			if (token.empty() || allAA.find(token[0]) == string::npos)
				continue;

			if (token.length() > 1
					&& (token[1] != '[' || token[token.length() - 1] != ']'
							|| (token[2] >= '0' && token[2] <= '9'))) {
				g_log->error("GENERAL",
						"Illegal user-defined modification token: " + token
								+ " . It must be of the form A[...]. Ignored.");
				continue;
			}

			if (!modType.empty()
					&& ((modType[0] >= '0' && modType[0] <= '9')
							|| (modType.find_first_of(",;|\\[]{}'\"#")
									!= string::npos))) {
				g_log->error("GENERAL",
						"Illegal user-defined modification type: " + modType
								+ " . It must not start with a number or contain special characters. Ignored.");
				continue;
			}

			char aa = token[0];
			double deltaMass = 0.0;

			if (modMass.empty())
				continue;

			if (modMass[0] == '+' || modMass[0] == '-' || modMass[0] == '.'
					|| (modMass[0] >= '0' && modMass[0] <= '9')) {
				deltaMass = atof(modMass.c_str());
			}

			if (fabs(deltaMass) < 0.0000001) {
				// crappy mass
				g_log->error("GENERAL",
						"No modification mass. Modification " + token + "|"
								+ modMass + "|" + modType + " ignored.");
				continue;
			}

			string userToken("");
			if (token.length() > 1)
				userToken = token;
			double newDeltaMass = deltaMass;
			string newModType(modType);

			if (!(Peptide::processNewMod(aa, newDeltaMass, newModType,
					userToken))) {
				g_log->error("GENERAL",
						"Modification " + token + "|" + modMass + "|" + modType
								+ " collides with existing specifications. Ignored.");
			} else {
				stringstream addModss;
				addModss << "Modification " << token << "|" << modMass << "|"
						<< modType;
				addModss << " successfully added as " << userToken << "|"
						<< newDeltaMass << "|" << newModType << " .";
				g_log->log("GENERAL", addModss.str());
			}
		}
	}

}

// printUsage - prints the usage of the program to console. 
static void printUsage() {

	cerr << "SpectraST (version " << SPECTRAST_VERSION << "."
			<< SPECTRAST_SUB_VERSION << ", " << szTPPVersionInfo
			<< ") by Henry Lam." << endl;
	cerr << endl;

	SpectraSTCreateParams::printUsage(cerr);
	cerr << endl;

	SpectraSTSearchParams::printUsage(cerr);
	cerr << endl;

	cerr << "Miscellaneous Options:" << endl;
	cerr << "         -V           Verbose mode." << endl;
	cerr << "         -Q           Quiet mode." << endl;
	cerr
			<< "         -L<file>     Specify name of log file. Default is \"spectrast.log\"."
			<< endl;
	cerr
			<< "         -M<file>     Specify name of user-defined modifications file. Default is \"spectrast.usermods\"."
			<< endl;
	cerr << endl;

	cerr.flush();
}

static void doTrainModel(SpectraSTTrainModelParams* trainModelParams,
		vector<string>& fileNames) {
	// activate all search options
	trainModelParams->finalizeOptions();

	int isList = SpectraSTFileList::isFileList(fileNames);

	if (isList < 0) {
		g_log->error("TrainModel",
				"Input files are of mixed list and non-list types. Exiting.");
	}

	if (isList == 1) {
		cout
				<< "Input files are .list files, try to add functions in SpectraSTFileList::Parse()"
				<< endl;
	} else {
		// input files are individual files containing training set of spectra.

	}
}

static void doPrediction(SpectraSTPredictionModelParams* predictionModelParams,
		vector<string>& fileNames) {
	// activate all search options
	predictionModelParams->finalizeOptions();

	int isList = SpectraSTFileList::isFileList(fileNames);

	if (isList < 0) {
		g_log->error("Prediction",
				"Input files are of mixed list and non-list types. Exiting.");
	}

	if (isList == 1) {
		cout
				<< "Input files are .list files, try to add functions in SpectraSTFileList::Parse()"
				<< endl;

	} else {
		// input files are individual files containing candidates of sequences or spectra for prediction.

	}
}
